//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------
/*
	C++ wrapper for libnoise compatibility.
*/

#pragma once

#include "libNoiseCL.h"
#include <math.h>

namespace noise
{
	// TODO: don't use this namespace
	using namespace Noise;

#if !defined(null)
#	define null	0
#endif

	typedef Noise::ENoiseQuality	NoiseQuality;
	
	const double PI = 3.1415926535897932385;
	const double SQRT_2 = 1.4142135623730950488;
	const double SQRT_3 = 1.7320508075688772935;
	const double DEG_TO_RAD = PI / 180.0;
	const double RAD_TO_DEG = 1.0 / DEG_TO_RAD;


namespace module
{

	//
	// Module Base
	//
	class Module
	{
	protected:
		Source	_src;

	private:
		static void _InitContext()
		{
			static bool firstCall = true;

			if ( !firstCall )
				return;

			firstCall = false;

			int isInited = false;
			nGetContextInteger( CONTEXT_CREATED, &isInited, 1 );

			if ( isInited )
				return;

			nCreateContext( CONTEXT_DEFAULT_FLAGS );
		}

	public:
		explicit
		Module (Source src) : _src(src)
		{
			_InitContext();
		}

		explicit
		Module (ESource type)
		{
			_src = nCreateSource( type );
		}

		Module (const Module &m)
		{
			_src = nCreateSourceCopy( m._src );
		}

		virtual ~Module ()
		{
			if ( _src != null )
			{
				nDeleteSource( _src );
				_src = null;
			}
		}
		
        virtual Module GetSourceModule (int index) const
		{
			Source src = null;
			nGetArgSource( _src, EArgument( SOURCE_0 + index ), &src );
			return Module( src );
		}
			
        virtual int GetSourceModuleCount () const
		{
			int count = 0;
			nGetSourceInteger( _src, SOURCES_COUNT, &count, 1 );
			return count;
		}

        virtual void SetSourceModule (int index, const Module& sourceModule)
		{
			nSetArgSource( _src, EArgument( SOURCE_0 + index ), sourceModule._src );
		}

		// temp:
		Source _GetSource() const
		{
			return _src;
		}
	};



	//
	// Abs
	//
	class Abs : public Module
	{
	public:
		Abs () : Module(ABS) {}
	};



	//
	// Add
	//
	class Add : public Module
	{
	public:
		Add () : Module(ADD) {}
	};



	//
	// Billow
	//
	const double DEFAULT_BILLOW_FREQUENCY = 1.0;
	const double DEFAULT_BILLOW_LACUNARITY = 2.0;
	const int DEFAULT_BILLOW_OCTAVE_COUNT = 6;
	const double DEFAULT_BILLOW_PERSISTENCE = 0.5;
	const noise::NoiseQuality DEFAULT_BILLOW_QUALITY = QUALITY_STD;
	const int DEFAULT_BILLOW_SEED = 0;
	const int BILLOW_MAX_OCTAVE = 30;

	class Billow : public Module
	{
	public:
		Billow () : Module(BILLOW) {}

		void SetFrequency (double frequency)					{ nSetArgDouble( _src, ARG_FREQUENCY, &frequency, 1 ); }
		void SetLacunarity (double lacunarity)					{ nSetArgDouble( _src, ARG_LACUNARITY, &lacunarity, 1 ); }
		void SetNoiseQuality (noise::NoiseQuality noiseQuality)	{ nSetArgInteger( _src, ARG_NOISE_QUALITY, (int *)&noiseQuality, 1 ); }
		void SetOctaveCount (int octaveCount)					{ nSetArgInteger( _src, ARG_OCTAVE_COUNT, &octaveCount, 1 ); }
		void SetPersistence (double persistence)				{ nSetArgDouble( _src, ARG_PERSISTENCE, &persistence, 1 ); }
		void SetSeed (int seed)									{ nSetArgInteger( _src, ARG_SEED, &seed, 1 ); }
	};


	//
	// Blend
	//
	class Blend : public Module
	{
	public:
		Blend () : Module(MIX) {}
	};


	//
	// Cache
	//
	/*class Cache : public Module
	{
	public:
		Cache () : Module(CACHE) {}
	};*/


	//
	// Checkerboard
	//
	class Checkerboard : public Module
	{
	public:
		Checkerboard () : Module(CHECKERBOARD) {}
	};


	//
	// Clamp
	//
	const double DEFAULT_CLAMP_LOWER_BOUND = -1.0;
	const double DEFAULT_CLAMP_UPPER_BOUND = 1.0;

	class Clamp : public Module
	{
	private:
		Source	_minValue,
				_maxValue;

	public:
		Clamp () : Module(CLAMP)
		{
			_minValue = nCreateSource( CONSTANT );
			_maxValue = nCreateSource( CONSTANT );
			nSetArgSource( _src, SOURCE_0, _minValue );
			nSetArgSource( _src, SOURCE_1, _maxValue );
			SetBounds( DEFAULT_CLAMP_LOWER_BOUND, DEFAULT_CLAMP_UPPER_BOUND );
		}
		
        void SetBounds (double lowerBound, double upperBound)
		{
			float min_value = (float)lowerBound;
			float max_value = (float)upperBound;

			nSetArgFloat( _minValue, ARG_CONST, &min_value, 1 );
			nSetArgFloat( _maxValue, ARG_CONST, &max_value, 1 );
		}
	};


	//
	// Const
	//
	const double DEFAULT_CONST_VALUE = 0.0;

	class Const : public Module
	{
	public:
		Const () : Module(CONSTANT) {}

		void SetConstValue (double constValue)
		{
			float value = (float)constValue;
			nSetArgFloat( _src, ARG_CONST, &value, 1 );
		}
	};


	//
	// Curve
	//
	struct ControlPoint
	{
		double inputValue;
		double outputValue;
	};

	class Curve : public Module
	{
	public:
		Curve () : Module(CURVE) {}

		void AddControlPoint (double inputValue, double outputValue)
		{
			float point[2] = { (float)inputValue, (float)outputValue };
			nSetArgFloat( _src, ARG_ADD_CONTROL_POINTS, point, 2 );
		}

		void ClearAllControlPoints ()
		{
			nSetArgFloat( _src, ARG_CONTROL_POINTS, null, 0 );
		}

		int GetControlPointCount () const
		{
			int count = 0;
			nGetSourceInteger( _src, CONTROL_POINTS_COUNT, &count, 1 );
			return count;
		}
	};


	//
	// Cylinders
	//
	const double DEFAULT_CYLINDERS_FREQUENCY = 1.0;

	class Cylinders : public Module
	{
	public:
		Cylinders () : Module(CYLINDERS) {}

		void SetFrequency (double frequency)
		{
			float freq = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &freq, 1 );
		}
	};


	//
	// Displace
	//
	class Displace : public Module
	{
	public:
		Displace () : Module(DISPLACE) {}

		void SetDisplaceModules (const Module& xDisplaceModule, const Module& yDisplaceModule, const Module& zDisplaceModule)
		{
			SetXDisplaceModule( xDisplaceModule );
			SetYDisplaceModule( yDisplaceModule );
			SetZDisplaceModule( zDisplaceModule );
		}

		void SetXDisplaceModule (const Module& xDisplaceModule)
		{
			SetSourceModule( 0, xDisplaceModule );
		}

		void SetYDisplaceModule (const Module& yDisplaceModule)
		{
			SetSourceModule( 1, yDisplaceModule );
		}

		void SetZDisplaceModule (const Module& zDisplaceModule)
		{
			SetSourceModule( 2, zDisplaceModule );
		}
	};


	//
	// Exponent
	//
	const double DEFAULT_EXPONENT = 1.0;

	class Exponent : public Module
	{
	private:
		Source	_exp;

	public:
		Exponent () : Module(NORM_EXP)
		{
			_exp = nCreateSource( CONSTANT );
			nSetArgSource( _src, SOURCE_1, _exp );
			SetExponent( DEFAULT_EXPONENT );
		}

		void SetExponent (double exponent)
		{
			float value = (float)exponent;
			nSetArgFloat( _exp, ARG_CONST, &value, 1 );
		}

		virtual int GetSourceModuleCount() const override	{ return 1; }
	};


	//
	// Invert
	//
	class Invert : public Module
	{
	public:
		Invert () : Module(INV) {}
	};


	//
	// Max
	//
	class Max : public Module
	{
	public:
		Max () : Module(MAX) {}
	};


	//
	// Min
	//
	class Min : public Module
	{
	public:
		Min () : Module(MIN) {}
	};


	//
	// Multiply
	//
	class Multiply : public Module
	{
	public:
		Multiply () : Module(MUL) {}
	};


	//
	// Perlin
	//
	const double DEFAULT_PERLIN_FREQUENCY = 1.0;
	const double DEFAULT_PERLIN_LACUNARITY = 2.0;
	const int DEFAULT_PERLIN_OCTAVE_COUNT = 6;
	const double DEFAULT_PERLIN_PERSISTENCE = 0.5;
	const noise::NoiseQuality DEFAULT_PERLIN_QUALITY = QUALITY_STD;
	const int DEFAULT_PERLIN_SEED = 0;
	const int PERLIN_MAX_OCTAVE = 30;

	class Perlin : public Module
	{
	public:
		Perlin () : Module(PERLIN) {}

		void SetFrequency (double frequency)
		{
			float value = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &value, 1 );
		}

		void SetLacunarity (double lacunarity)
		{
			float value = (float)lacunarity;
			nSetArgFloat( _src, ARG_LACUNARITY, &value, 1 );
		}

		void SetNoiseQuality (noise::NoiseQuality noiseQuality)
		{
			nSetArgInteger( _src, ARG_NOISE_QUALITY, (int *)&noiseQuality, 1 );
		}

		void SetOctaveCount (int octaveCount)
		{
			nSetArgInteger( _src, ARG_OCTAVE_COUNT, &octaveCount, 1 );
		}

		void SetPersistence (double persistence)
		{
			float value = (float)persistence;
			nSetArgFloat( _src, ARG_PERSISTENCE, &value, 1 );
		}

		void SetSeed (int seed)
		{
			nSetArgInteger( _src, ARG_SEED, &seed, 1 );
		}
	};


	//
	// Power
	//
	class Power : public Module
	{
	public:
		Power () : Module(POW) {}
	};


	//
	// RidgedMulti
	//
	const double DEFAULT_RIDGED_FREQUENCY = 1.0;
	const double DEFAULT_RIDGED_LACUNARITY = 2.0;
	const int DEFAULT_RIDGED_OCTAVE_COUNT = 6;
	const noise::NoiseQuality DEFAULT_RIDGED_QUALITY = QUALITY_STD;
	const int DEFAULT_RIDGED_SEED = 0;
	const int RIDGED_MAX_OCTAVE = 30;

	class RidgedMulti : public Module
	{
	public:
		RidgedMulti () : Module(RIDGED_MULTI) {}

		void SetFrequency (double frequency)
		{
			float value = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &value, 1 );
		}

		void SetLacunarity (double lacunarity)
		{
			float value = (float)lacunarity;
			nSetArgFloat( _src, ARG_LACUNARITY, &value, 1 );
		}

		void SetNoiseQuality (noise::NoiseQuality noiseQuality)
		{
			nSetArgInteger( _src, ARG_NOISE_QUALITY, (int *)&noiseQuality, 1 );
		}

		void SetOctaveCount (int octaveCount)
		{
			nSetArgInteger( _src, ARG_OCTAVE_COUNT, &octaveCount, 1 );
		}

		void SetSeed (int seed)
		{
			nSetArgInteger( _src, ARG_SEED, &seed, 1 );
		}
	};


	//
	// RotatePoint
	//
	const double DEFAULT_ROTATE_X = 0.0;
	const double DEFAULT_ROTATE_Y = 0.0;
	const double DEFAULT_ROTATE_Z = 0.0;

	class RotatePoint : public Module
	{
	private:
		float	_rotationMat[9];

	public:
		RotatePoint () : Module(ROTATE_POINT) {}

		void SetAngles (double xAngle, double yAngle, double zAngle)
		{
			double xCos, yCos, zCos, xSin, ySin, zSin;
			xCos = cos (xAngle * DEG_TO_RAD);
			yCos = cos (yAngle * DEG_TO_RAD);
			zCos = cos (zAngle * DEG_TO_RAD);
			xSin = sin (xAngle * DEG_TO_RAD);
			ySin = sin (yAngle * DEG_TO_RAD);
			zSin = sin (zAngle * DEG_TO_RAD);

			_rotationMat[0] = float( ySin * xSin * zSin + yCos * zCos );
			_rotationMat[1] = float( xCos * zSin );
			_rotationMat[2] = float( ySin * zCos - yCos * xSin * zSin );
			_rotationMat[3] = float( ySin * xSin * zCos - yCos * zSin );
			_rotationMat[4] = float( xCos * zCos );
			_rotationMat[5] = float( -yCos * xSin * zCos - ySin * zSin );
			_rotationMat[6] = float( -ySin * xCos );
			_rotationMat[7] = float( xSin );
			_rotationMat[8] = float( yCos * xCos );

			nSetArgFloat( _src, ARG_ROTATION_MAT3, _rotationMat, 9 );
		}
	};


	//
	// ScaleBias
	//
	const double DEFAULT_BIAS = 0.0;
	const double DEFAULT_SCALE = 1.0;

	class ScaleBias : public Module
	{
	public:
		ScaleBias () : Module(SCALE_BIAS) {}

		void SetBias (double bias)
		{
			float value = (float)bias;
			nSetArgFloat( _src, ARG_BIAS, &value, 1 );
		}

		void SetScale (double scale)
		{
			float value = (float)scale;
			nSetArgFloat( _src, ARG_SCALE, &value, 1 );
		}
	};


	//
	// Select
	//
	const double DEFAULT_SELECT_EDGE_FALLOFF = 0.0;
	const double DEFAULT_SELECT_LOWER_BOUND = -1.0;
	const double DEFAULT_SELECT_UPPER_BOUND = 1.0;

	class Select : public Module
	{
	public:
		Select () : Module(SELECT) {}

		void SetBounds (double lowerBound, double upperBound)
		{
			float lower_bound = (float)lowerBound;
			float upper_bound = (float)upperBound;
			nSetArgFloat( _src, ARG_LOWER_BOUND, &lower_bound, 1 );
			nSetArgFloat( _src, ARG_UPPER_BOUND, &upper_bound, 1 );
		}

		void SetControlModule (const Module& controlModule)
		{
			SetSourceModule( 2, controlModule );
		}

		void SetEdgeFalloff (double edgeFalloff)
		{
			float value = (float)edgeFalloff;
			nSetArgFloat( _src, ARG_EDGE_FALLOFF, &value, 1 );
		}
	};


	//
	// Spheres
	//
	const double DEFAULT_SPHERES_FREQUENCY = 1.0;

	class Spheres : public Module
	{
	public:
		Spheres () : Module(SPHERES) {}

		void SetFrequency (double frequency)
		{
			float value = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &value, 1 );
		}
	};


	//
	// Terrace
	//
	class Terrace : public Module
	{
	public:
		Terrace () : Module(TERRACE) {}

		void AddControlPoint (double value)
		{
			float point = (float)value;
			nSetArgFloat( _src, ARG_ADD_CONTROL_POINTS, &point, 1 );
		}

		void ClearAllControlPoints ()
		{
			nSetArgFloat( _src, ARG_CONTROL_POINTS, null, 0 );
		}

		int GetControlPointCount () const
		{
			int count = 0;
			nGetSourceInteger( _src, CONTROL_POINTS_COUNT, &count, 1 );
			return count;
		}

		void InvertTerraces (bool invert = true)
		{
			int value = invert;
			nSetArgInteger( _src, ARG_INVERT_TERRACES, &value, 1 );
		}

		void MakeControlPoints (int controlPointCount)
		{
			ClearAllControlPoints ();

			double terraceStep = 2.0 / ((double)controlPointCount - 1.0);
			double curValue = -1.0;
			for (int i = 0; i < (int)controlPointCount; i++) {
				AddControlPoint (curValue);
				curValue += terraceStep;
			}
		}
	};


	//
	// TranslatePoint
	//
	const double DEFAULT_TRANSLATE_POINT_X = 0.0;
	const double DEFAULT_TRANSLATE_POINT_Y = 0.0;
	const double DEFAULT_TRANSLATE_POINT_Z = 0.0;

	class TranslatePoint : public Module
	{
	public:
		TranslatePoint () : Module(TRANSLATE_POINT) {}

		void SetTranslation (double translation)
		{
			SetTranslation( translation, translation, translation );
		}

		void SetTranslation (double xTranslation, double yTranslation, double zTranslation)
		{
			float translation[3] = { (float)xTranslation, (float)yTranslation, (float)zTranslation };
			nSetArgFloat( _src, ARG_TRANSLATE, translation, 1 );
		}
	};


	//
	// Turbulence
	//
	const double DEFAULT_TURBULENCE_FREQUENCY = DEFAULT_PERLIN_FREQUENCY;
	const double DEFAULT_TURBULENCE_POWER = 1.0;
	const int DEFAULT_TURBULENCE_ROUGHNESS = 3;
	const int DEFAULT_TURBULENCE_SEED = DEFAULT_PERLIN_SEED;

	class Turbulence : public Module
	{
	public:
		Turbulence () : Module(TURBULENCE) {}

		void SetFrequency (double frequency)
		{
			float value = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &value, 1 );
		}

		void SetPower (double power)
		{
			float value = (float)power;
			nSetArgFloat( _src, ARG_POWER, &value, 1 );
		}

		void SetRoughness (int roughness)
		{
			nSetArgInteger( _src, ARG_ROUGHNESS, &roughness, 1 );
		}

		void SetSeed (int seed)
		{
			nSetArgInteger( _src, ARG_SEED, &seed, 1 );
		}
	};


	//
	// Voronoi
	//
	const double DEFAULT_VORONOI_DISPLACEMENT = 1.0;
	const double DEFAULT_VORONOI_FREQUENCY = 1.0;
	const int DEFAULT_VORONOI_SEED = 0;

	class Voronoi : public Module
	{
	public:
		Voronoi () : Module(VORONOI) {}

		void EnableDistance (bool enable = true)
		{
			int value = enable;
			nSetArgInteger( _src, ARG_ENABLE_DISTANCE, &value, 1 );
		}

		void SetDisplacement (double displacement)
		{
			float value = (float)displacement;
			nSetArgFloat( _src, ARG_DISPLACEMENT, &value, 1 );
		}

		void SetFrequency (double frequency)
		{
			float value = (float)frequency;
			nSetArgFloat( _src, ARG_FREQUENCY, &value, 1 );
		}

		void SetSeed (int seed)
		{
			nSetArgInteger( _src, ARG_SEED, &seed, 1 );
		}
	};

}	// module



namespace model
{


}	// model


namespace utils
{

	//
	// Color
	//
	struct Color
	{
		int	rgba;

		Color () : rgba(0) {}
		Color (int r, int g, int b, int a) : rgba((r & 0xFF) | ((g & 0xFF)<<8) | ((b & 0xFF)<<16) | ((a & 0xFF)<<24)) {}

		void _ToFloat (float color[4]) const
		{
			color[0] = float(rgba & 0xFF) / 255.0f;
			color[1] = float((rgba >> 8) & 0xFF) / 255.0f;
			color[2] = float((rgba >> 16) & 0xFF) / 255.0f;
			color[3] = float((rgba >> 24) & 0xFF) / 255.0f;
		}
	};


	//
	// Gradient
	//
	class Gradient : public module::Module
	{
	public:
		Gradient () : Module(GRADIENT) {}

		void AddGradientPoint (double value, const Color &color)
		{
			float	data[5] = { (float)value, 0.0f, 0.0f, 0.0f, 0.0f };

			color._ToFloat( data+1 );
			nSetArgFloat( _src, ARG_ADD_COLOR_POINTS, data, 5 );
		}

		void ClearGradient ()
		{
			nSetArgFloat( _src, ARG_COLOR_POINTS, null, 0 );
		}
	};

}	// utils


namespace render
{

	//
	// Renderer
	//
	class Renderer
	{
	protected:
		Program		_prog;

	public:
		Renderer () : _prog(null)
		{}

		void SetSourceModule (const module::Module &sourceModule)
		{
			if ( _prog == null )
			{
				_prog = nCreateProgram( OPENCL_PROGRAM );
			}
			nBuildProgram( _prog, sourceModule._GetSource(), DEFAULT_OPTIONS );
		}

		void RenderToMemory (int width, int height, int depth, int channels, float *data)
		{
			EPixelFormat	format = R;

			if ( channels < 1 )	format = R;		else
			if ( channels > 4 )	format = RGBA;	else
								format = EPixelFormat(channels);

			nRenderToMemory( _prog, width, height, depth, 0, 0, format, FLOAT32, data );
		}

		void RenderToTextureGL (GLenum target, GLuint id, int level = 0)
		{
			nRenderToTextureGL( _prog, target, id, level );
		}
	};


	//
	// Context
	//
	class RenderContext
	{
	public:
		RenderContext ()
		{
			nCreateContext( CONTEXT_DEFAULT_FLAGS );
		}

		RenderContext (void *dc, void *rc)
		{
			nCreateContextFromGL( dc, rc, CONTEXT_DEFAULT_FLAGS );
		}

		~RenderContext ()
		{
			nDeleteContext();
		}
	};

}	// render

}	// noise
